{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "CCQY7jpBfMur"
   },
   "source": [
    "##### Copyright 2019 The TensorFlow Authors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "cellView": "form",
    "colab": {},
    "colab_type": "code",
    "id": "z6X9omPnfO_h"
   },
   "outputs": [],
   "source": [
    "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "# https://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "F1xIRPtY0E1w"
   },
   "source": [
    "# 케라스: 빠르게 훑어보기"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "VyOjQZHhZxaA"
   },
   "source": [
    "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/overview\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />TensorFlow.org에서 보기</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/ko/guide/keras/overview.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />구글 코랩(Colab)에서 실행하기</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/ko/guide/keras/overview.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />깃허브(GitHub) 소스 보기</a>\n",
    "  </td>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note: 이 문서는 텐서플로 커뮤니티에서 번역했습니다. 커뮤니티 번역 활동의 특성상 정확한 번역과 최신 내용을 반영하기 위해 노력함에도\n",
    "불구하고 [공식 영문 문서](https://www.tensorflow.org/?hl=en)의 내용과 일치하지 않을 수 있습니다.\n",
    "이 번역에 개선할 부분이 있다면\n",
    "[tensorflow/docs](https://github.com/tensorflow/docs) 깃헙 저장소로 풀 리퀘스트를 보내주시기 바랍니다.\n",
    "문서 번역이나 리뷰에 참여하려면\n",
    "[docs-ko@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ko)로\n",
    "메일을 보내주시기 바랍니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "IsK5aF2xZ-40"
   },
   "source": [
    "## tf.keras 임포트\n",
    "\n",
    "`tf.keras`는 [케라스 API 명세](https://keras.io){:.external}의 텐서플로 구현입니다. `tf.keras`는 머신러닝 모델을 만들고 훈련하기 위한 고수준 API로서 텐서플로의 특수 기능을 모두 지원합니다. 여기에는 [즉시 실행](#eager_execution), `tf.data` 파이프라인(pipeline), [Estimators](./estimators.md)가 포함됩니다. `tf.keras`를 이용하면 유연성과 성능을 손해보지 않고 텐서플로를 쉽게 사용할 수 있습니다.\n",
    "\n",
    "`tf.keras`를 임포트하여 텐서플로 프로그램을 시작합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "Qoc055N3wiUG"
   },
   "outputs": [],
   "source": [
    "!pip install pyyaml  # pyyaml은 선택사항입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "TgPcBFru0E1z"
   },
   "outputs": [],
   "source": [
    "from __future__ import absolute_import, division, print_function, unicode_literals, unicode_literals\n",
    "\n",
    "!pip install tensorflow-gpu==2.0.0-rc1\n",
    "import tensorflow as tf\n",
    "\n",
    "from tensorflow import keras"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "lj03RamP0E13"
   },
   "source": [
    "`tf.keras`는 케라스 API와 호환되는 어떤 코드라도 실행시킬 수 있지만 다음 사항을 유념하세요:\n",
    "\n",
    "* 최신 텐서플로 릴리스에 포함된 `tf.keras` 버전은 PyPI에 있는 최신 `keras` 버전과 같지 않을 수 있습니다. `tf.keras.__version__`을 확인해 보세요.\n",
    "* [모델의 가중치를 저장](#weights_only)할 때 `tf.keras`는 기본적으로 [체크포인트 포맷](./checkpoints.md)을 사용합니다. HDF5를 사용하려면 `save_format='h5'`로 설정하세요."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "7e1LPcXx0gR6"
   },
   "source": [
    "## 간단한 모델 만들기\n",
    "\n",
    "### Sequential 모델\n",
    "\n",
    "케라스에서는 *층(layer)*을 조합하여 *모델(model)*을 만듭니다. 모델은 (일반적으로) 층의 그래프입니다. 가장 흔한 모델 구조는 층을 차례대로 쌓은 `tf.keras.Sequential` 모델입니다.\n",
    "\n",
    "간단한 완전 연결(fully-connected) 네트워크(즉, 다층 퍼셉트론(multi-layer perceptron))를 만들어 보겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "WM-DUVQB0E14"
   },
   "outputs": [],
   "source": [
    "from tensorflow.keras import layers\n",
    "\n",
    "model = tf.keras.Sequential()\n",
    "# 64개의 유닛을 가진 완전 연결 층을 모델에 추가합니다:\n",
    "model.add(layers.Dense(64, activation='relu'))\n",
    "# 또 하나를 추가합니다:\n",
    "model.add(layers.Dense(64, activation='relu'))\n",
    "# 10개의 출력 유닛을 가진 소프트맥스 층을 추가합니다:\n",
    "model.add(layers.Dense(10, activation='softmax'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[여기](https://github.com/tensorflow/docs/blob/master/site/en/tutorials/quickstart/beginner.ipynb)에서 `Sequential` 모델을 어떻게 사용하는지 간단하지만 완전한 예제를 볼 수 있습니다.\n",
    "\n",
    "`Sequential`보다 더 고수준의 모델을 구성하는 방법을 배우려면 다음을 참고하세요:\n",
    "- [케라스 함수형 API 가이드](./functional.ipynb)\n",
    "- [클래스 상속을 통하여 층과 모델을 밑바닥부터 만드는 방법](./custom_layers_and_models.ipynb)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "-ztyTipu0E18"
   },
   "source": [
    "### 층 설정\n",
    "\n",
    "`tf.keras.layers` 아래의 클래스들은 일부 생성자 매개변수를 공통으로 가지고 있습니다:\n",
    "\n",
    "* `activation`: 층의 활성화 함수를 설정합니다. 이 매개변수에는 기본으로 제공되는 함수의 이름을 쓰거나\n",
    "  호출 가능한 객체를 지정할 수 있습니다. 기본값은 활성화 함수를 적용하지 않는 것입니다.\n",
    "* `kernel_initializer`와 `bias_initializer`: 층의 가중치(weight)(커널(kernel)과 절편(bias))를 초기화하는 방법입니다. 내장 함수나 호출 가능한 객체를 지정합니다. 기본값은 `\"glorot_uniform\"` 초기화입니다.\n",
    "* `kernel_regularizer`와 `bias_regularizer`: L1 또는 L2 규제(regularization)와 같이 층의 가중치(커널과 절편)에 적용할 규제 방법을 지정합니다. 기본값은 규제를 적용하지 않는 것입니다.\n",
    "\n",
    "다음 코드는 여러가지 생성자 매개변수를 사용하여 `tf.keras.layers.Dense` 층의 객체를 만드는 예입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "sJ4AOn090E2A"
   },
   "outputs": [],
   "source": [
    "# 시그모이드 활성화 층을 만듭니다:\n",
    "layers.Dense(64, activation='sigmoid')\n",
    "# 또는 다음도 가능합니다:\n",
    "layers.Dense(64, activation=tf.keras.activations.sigmoid)\n",
    "\n",
    "# 커널 행렬에 L1 규제가 적용된 선형 활성화 층. 하이퍼파라미터 0.01은 규제의 양을 조절합니다:\n",
    "layers.Dense(64, kernel_regularizer=tf.keras.regularizers.l1(0.01))\n",
    "\n",
    "# 절편 벡터에 L2 규제가 적용된 선형 활성화 층. 하이퍼파라미터 0.01은 규제의 양을 조절합니다:\n",
    "layers.Dense(64, bias_regularizer=tf.keras.regularizers.l2(0.01))\n",
    "\n",
    "# 커널을 랜덤한 직교 행렬로 초기화한 선형 활성화 층:\n",
    "layers.Dense(64, kernel_initializer='orthogonal')\n",
    "\n",
    "# 절편 벡터를 상수 2.0으로 설정한 선형 활성화 층:\n",
    "layers.Dense(64, bias_initializer=tf.keras.initializers.Constant(2.0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "9NR6reyk0E2A"
   },
   "source": [
    "## 훈련과 평가\n",
    "\n",
    "### 훈련 준비\n",
    "\n",
    "모델을 구성한 후 `compile` 메서드를 호출하여 학습 과정을 설정합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "sJ4AOn090E2A"
   },
   "outputs": [],
   "source": [
    "model = tf.keras.Sequential([\n",
    "# 64개의 유닛을 가진 완전 연결 층을 모델에 추가합니다:\n",
    "layers.Dense(64, activation='relu', input_shape=(32,)),\n",
    "# 또 하나를 추가합니다:\n",
    "layers.Dense(64, activation='relu'),\n",
    "# 10개의 출력 유닛을 가진 소프트맥스 층을 추가합니다:\n",
    "layers.Dense(10, activation='softmax')])\n",
    "\n",
    "model.compile(optimizer=tf.keras.optimizers.Adam(0.001),\n",
    "              loss='categorical_crossentropy',\n",
    "              metrics=['accuracy'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "HG-RAa9F0E2D"
   },
   "source": [
    "`tf.keras.Model.compile`에는 세 개의 중요한 매개변수가 있습니다:\n",
    "\n",
    "* `optimizer`: 훈련 과정을 설정합니다. `tf.keras.optimizers.Adam`이나\n",
    "  `tf.keras.optimizers.SGD`와 같은 `tf.keras.optimizers` 아래의 옵티마이저 객체를 전달합니다. 기본 매개변수를 사용할 경우 `'adam'`이나 `'sgd'`와 같이 문자열로 지정할 수도 있습니다.\n",
    "* `loss`: 최적화 과정에서 최소화될 손실 함수(loss function)를 설정합니다. 평균 제곱 오차(`mse`)와 `categorical_crossentropy`, `binary_crossentropy` 등이 자주 사용됩니다. 손실 함수의 이름을 지정하거나 `tf.keras.losses` 모듈 아래의 호출 가능한 객체를 전달할 수 있습니다.\n",
    "* `metrics`: 훈련을 모니터링하기 위해 사용됩니다. 이름이나 `tf.keras.metrics` 모듈 아래의 호출 가능한 객체입니다.\n",
    "* 추가적으로 모델의 훈련과 평가를 즉시 실행하려면 `run_eagerly=True` 매개변수를 전달할 수 있습니다.\n",
    "\n",
    "다음 코드는 모델 훈련을 설정하는 몇 가지 예를 보여줍니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "St4Mgdar0E2E"
   },
   "outputs": [],
   "source": [
    "# 평균 제곱 오차로 회귀 모델을 설정합니다.\n",
    "model.compile(optimizer=tf.keras.optimizers.Adam(0.01),\n",
    "              loss='mse',       # 평균 제곱 오차\n",
    "              metrics=['mae'])  # 평균 절댓값 오차\n",
    "\n",
    "# 크로스엔트로피 손실 함수로 분류 모델을 설정합니다.\n",
    "model.compile(optimizer=tf.keras.optimizers.RMSprop(0.01),\n",
    "              loss=tf.keras.losses.CategoricalCrossentropy(),\n",
    "              metrics=[tf.keras.metrics.CategoricalAccuracy()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "yjI5rbi80E2G"
   },
   "source": [
    "### 넘파이 데이터를 사용한 훈련\n",
    "\n",
    "데이터셋이 작은 경우 [넘파이](https://www.numpy.org/){:.external}(NumPy) 배열을 메모리에 적재하여 모델을 훈련하고 평가합니다. 모델은 `fit` 메서드를 통해서 훈련 데이터를 학습합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "3CvP6L-m0E2I"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "data = np.random.random((1000, 32))\n",
    "labels = np.random.random((1000, 10))\n",
    "\n",
    "model.fit(data, labels, epochs=10, batch_size=32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "N-pnVaFe0E2N"
   },
   "source": [
    "`tf.keras.Model.fit`에는 세 개의 중요한 매개변수가 있습니다:\n",
    "\n",
    "* `epochs`: 훈련은 *에포크*(epoch)로 구성됩니다. 한 에포크는 전체 입력 데이터를 한번 순회하는 것입니다(작은 배치로 나누어 수행됩니다).\n",
    "* `batch_size`: 넘파이 데이터를 전달하면 모델은 데이터를 작은 배치로 나누고 훈련 과정에서 이 배치를 순회합니다. 이 정수 값은 배치의 크기를 지정합니다. 전체 샘플 개수가 배치 크기로 나누어 떨어지지 않으면 마지막 배치의 크기는 더 작을 수 있습니다.\n",
    "* `validation_data`: 모델의 프로토타입(prototype)을 만들 때는 검증 데이터(validation data)에서 간편하게 성능을 모니터링해야 합니다. 입력과 레이블(label)의 튜플을 이 매개변수로 전달하면 에포크가 끝날 때마다 추론 모드(inference mode)에서 전달된 데이터의 손실과 측정 지표를 출력합니다.\n",
    "\n",
    "다음이 `validation_data`를 사용하는 예입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "gFcXzVQa0E2N"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "data = np.random.random((1000, 32))\n",
    "labels = np.random.random((1000, 10))\n",
    "\n",
    "val_data = np.random.random((100, 32))\n",
    "val_labels = np.random.random((100, 10))\n",
    "\n",
    "model.fit(data, labels, epochs=10, batch_size=32,\n",
    "          validation_data=(val_data, val_labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "-6ImyXzz0E2Q"
   },
   "source": [
    "### tf.data 데이터셋을 사용한 훈련\n",
    "\n",
    "[데이터셋 API](./datasets.md)를 사용하여 대규모 데이터셋이나 복수의 장치로 확장시킬 수 있습니다. `fit` 메서드에 `tf.data.Dataset` 객체를 전달합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "OziqhpIj0E2R"
   },
   "outputs": [],
   "source": [
    "# 예제 `Dataset` 객체를 만듭니다:\n",
    "dataset = tf.data.Dataset.from_tensor_slices((data, labels))\n",
    "dataset = dataset.batch(32)\n",
    "\n",
    "# Dataset에서 `fit` 메서드를 호출할 때 `steps_per_epoch` 설정을 잊지 마세요.\n",
    "model.fit(dataset, epochs=10, steps_per_epoch=30)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "I7BcMHkB0E2U"
   },
   "source": [
    "여기에서 `fit` 메서드는 `steps_per_epoch` 매개변수를 사용합니다. 다음 에포크로 넘어가기 전에 모델이 수행할 훈련 단계 횟수입니다. `Dataset`이 배치 데이터를 생성하기 때문에 `batch_size`가 필요하지 않습니다.\n",
    "\n",
    "`Dataset`은 검증 데이터에도 사용할 수 있습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "YPMb3A0N0E2V"
   },
   "outputs": [],
   "source": [
    "dataset = tf.data.Dataset.from_tensor_slices((data, labels))\n",
    "dataset = dataset.batch(32)\n",
    "\n",
    "val_dataset = tf.data.Dataset.from_tensor_slices((val_data, val_labels))\n",
    "val_dataset = val_dataset.batch(32)\n",
    "\n",
    "model.fit(dataset, epochs=10,\n",
    "          validation_data=val_dataset)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "IgGdlXso0E2X"
   },
   "source": [
    "### 평가와 예측\n",
    "\n",
    "`tf.keras.Model.evaluate`와 `tf.keras.Model.predict` 메서드에는 넘파이 배열이나 `tf.data.Dataset`을 사용할 수 있습니다.\n",
    "\n",
    "주어진 데이터로 추론 모드의 손실이나 지표를 *평가*합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "mhDbOHEK0E2Y"
   },
   "outputs": [],
   "source": [
    "data = np.random.random((1000, 32))\n",
    "labels = np.random.random((1000, 10))\n",
    "\n",
    "model.evaluate(data, labels, batch_size=32)\n",
    "\n",
    "model.evaluate(dataset, steps=30)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "UXUTmDfb0E2b"
   },
   "source": [
    "주어진 데이터로 추론 모드에서 마지막 층의 출력을 *예측*하여 넘파이 배열로 반환합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "9e3JsSoQ0E2c"
   },
   "outputs": [],
   "source": [
    "result = model.predict(data, batch_size=32)\n",
    "print(result.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "맞춤형 훈련 반복을 밑바닥부터 작성하는 방법을 포함하여 훈련과 평가에 대한 완전한 설명은 [훈련과 평가 가이드](./train_and_evaluate.ipynb)를 참고하세요."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "fzEOW4Cn0E2h"
   },
   "source": [
    "## 고급 모델 만들기\n",
    "\n",
    "### 함수형 API\n",
    "\n",
    "`tf.keras.Sequential` 모델은 단순히 층을 쌓은 것으로 임의의 구조를 표현할 수 없습니다. [케라스 함수형 API](./functional.ipynb)를 사용하면 다음과 같은 복잡한 모델 구조를 만들 수 있습니다:\n",
    "\n",
    "* 다중 입력 모델,\n",
    "* 다중 출력 모델,\n",
    "* 층을 공유하는 모델 (동일한 층을 여러번 호출합니다),\n",
    "* 데이터 흐름이 차례대로 진행되지 않는 모델 (예를 들면 잔차 연결(residual connections)).\n",
    "\n",
    "함수형 API로 모델을 만드는 방식은 다음과 같습니다:\n",
    "\n",
    "1. 하나의 층 객체는 호출 가능하고 텐서를 반환합니다.\n",
    "2. `tf.keras.Model` 객체를 정의하기 위해 입력 텐서와 출력 텐서를 사용합니다.\n",
    "3. 이 모델은 `Sequential` 모델과 동일한 방식으로 훈련됩니다.\n",
    "\n",
    "다음 코드는 함수형 API를 사용하여 간단한 완전 연결 네트워크를 만드는 예입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "mROj832r0E2i"
   },
   "outputs": [],
   "source": [
    "inputs = tf.keras.Input(shape=(32,))  # 입력 플레이스홀더를 반환합니다.\n",
    "\n",
    "# 층 객체는 텐서를 사용하여 호출되고 텐서를 반환합니다.\n",
    "x = layers.Dense(64, activation='relu')(inputs)\n",
    "x = layers.Dense(64, activation='relu')(x)\n",
    "predictions = layers.Dense(10, activation='softmax')(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "AFmspHeG1_W7"
   },
   "source": [
    "입력과 출력을 사용해 모델의 객체를 만듭니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "5k5uzlyu16HM"
   },
   "outputs": [],
   "source": [
    "model = tf.keras.Model(inputs=inputs, outputs=predictions)\n",
    "\n",
    "# 컴파일 단계는 훈련 과정을 설정합니다.\n",
    "model.compile(optimizer=tf.keras.optimizers.RMSprop(0.001),\n",
    "              loss='categorical_crossentropy',\n",
    "              metrics=['accuracy'])\n",
    "\n",
    "# 5번의 에포크 동안 훈련합니다.\n",
    "model.fit(data, labels, batch_size=32, epochs=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "EcKSLH3i0E2k"
   },
   "source": [
    "### 모델 클래스 상속\n",
    "\n",
    "`tf.keras.Model` 클래스를 상속하고 자신만의 정방향 패스(forward pass)을 정의하여 완전히 커스터마이징된 모델을 만들 수 있습니다. `__init__` 메서드에서 층을 만들어 클래스 객체의 속성으로 지정합니다. 정방향 패스는 `call` 메서드에 정의합니다.\n",
    "\n",
    "[즉시 실행](./eager.md)이 활성화되어 있을 때 정방향 패스를 명령형 프로그래밍 방식으로 작성할 수 있기 때문에 모델 클래스 상속이 매우 유용합니다.\n",
    "\n",
    "노트: 정방향 패스를 *항상* 명령형 프로그래밍 방식으로 실행하려면 `super` 객체의 생성자를 호출할 때 `dynamic=True`를 지정하세요.\n",
    "\n",
    "중요 포인트: 작업에 맞는 API를 사용하세요. 모델 클래스 상속은 유연성을 제공하지만 복잡도가 증가하고 사용자 오류가 발생할 가능성이 높아집니다. 가능한한 함수형 API를 사용하세요.\n",
    "\n",
    "다음 코드는 `tf.keras.Model`의 클래스를 상속하여 명령형 프로그래밍 방식으로 실행할 필요가 없는 정방향 패스를 구현한 예입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "KLiHWzcn2Fzk"
   },
   "outputs": [],
   "source": [
    "class MyModel(tf.keras.Model):\n",
    "\n",
    "  def __init__(self, num_classes=10):\n",
    "    super(MyModel, self).__init__(name='my_model')\n",
    "    self.num_classes = num_classes\n",
    "    # 층을 정의합니다.\n",
    "    self.dense_1 = layers.Dense(32, activation='relu')\n",
    "    self.dense_2 = layers.Dense(num_classes, activation='sigmoid')\n",
    "\n",
    "  def call(self, inputs):\n",
    "    # 정방향 패스를 정의합니다.\n",
    "    # `__init__` 메서드에서 정의한 층을 사용합니다.\n",
    "    x = self.dense_1(inputs)\n",
    "    return self.dense_2(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ShDD4fv72KGc"
   },
   "source": [
    "새 모델 클래스의 객체를 만듭니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "42C-qQHm0E2l"
   },
   "outputs": [],
   "source": [
    "model = MyModel(num_classes=10)\n",
    "\n",
    "# 컴파일 단계는 훈련 과정을 설정합니다.\n",
    "model.compile(optimizer=tf.keras.optimizers.RMSprop(0.001),\n",
    "              loss='categorical_crossentropy',\n",
    "              metrics=['accuracy'])\n",
    "\n",
    "# 5번의 에포크 동안 훈련합니다.\n",
    "model.fit(data, labels, batch_size=32, epochs=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "yqRQiKj20E2o"
   },
   "source": [
    "### 맞춤형 층\n",
    "\n",
    "맞춤형 층(custom layer)을 만들려면 `tf.keras.layers.Layer` 클래스를 상속하고 다음 메서드를 구현합니다:\n",
    "\n",
    "* `__init__`: 이 층에서 사용되는 하위 층을 정의할 수 있습니다.\n",
    "* `build`: 층의 가중치를 만듭니다. `add_weight` 메서드를 사용해 가중치를 추가합니다.\n",
    "* `call`: 정방향 패스를 구현합니다.\n",
    "\n",
    "다음 코드는 입력과 커널 행렬의 `matmul` 계산을 구현한 맞춤형 층의 예입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "l7BFnIHr2WNc"
   },
   "outputs": [],
   "source": [
    "class MyLayer(layers.Layer):\n",
    "\n",
    "  def __init__(self, output_dim, **kwargs):\n",
    "    self.output_dim = output_dim\n",
    "    super(MyLayer, self).__init__(**kwargs)\n",
    "\n",
    "  def build(self, input_shape):\n",
    "    # 이 층에서 훈련할 가중치 변수를 만듭니다.\n",
    "    self.kernel = self.add_weight(name='kernel',\n",
    "                                  shape=(input_shape[1], self.output_dim),\n",
    "                                  initializer='uniform',\n",
    "                                  trainable=True)\n",
    "\n",
    "  def call(self, inputs):\n",
    "    return tf.matmul(inputs, self.kernel)\n",
    "\n",
    "  def get_config(self):\n",
    "    base_config = super(MyLayer, self).get_config()\n",
    "    base_config['output_dim'] = self.output_dim\n",
    "    return base_config\n",
    "\n",
    "  @classmethod\n",
    "  def from_config(cls, config):\n",
    "    return cls(**config)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "8wXDRgXV2ZrF"
   },
   "source": [
    "맞춤형 층을 사용하여 모델을 만듭니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "uqH-cY0h0E2p"
   },
   "outputs": [],
   "source": [
    "model = tf.keras.Sequential([\n",
    "    MyLayer(10),\n",
    "    layers.Activation('softmax')])\n",
    "\n",
    "# 컴파일 단계는 훈련 과정을 설정합니다.\n",
    "model.compile(optimizer=tf.keras.optimizers.RMSprop(0.001),\n",
    "              loss='categorical_crossentropy',\n",
    "              metrics=['accuracy'])\n",
    "\n",
    "# 5번의 에포크 동안 훈련합니다.\n",
    "model.fit(data, labels, batch_size=32, epochs=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "클래스 상속을 통해 맞춤형 층과 모델을 만드는 더 자세한 정보는 [맞춤형 층과 모델을 만드는 방법](./custom_layers_and_models.ipynb)을 참고하세요."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Lu8cc3AJ0E2v"
   },
   "source": [
    "## 콜백\n",
    "\n",
    "콜백(callback)은 훈련하는 동안 모델의 동작을 변경하고 확장하기 위해 전달하는 객체입니다. 자신만의 콜백을 작성하거나 다음과 같은 내장 `tf.keras.callbacks`을 사용할 수 있습니다:\n",
    "\n",
    "* `tf.keras.callbacks.ModelCheckpoint`: 일정 간격으로 모델의 체크포인트를 저장합니다.\n",
    "* `tf.keras.callbacks.LearningRateScheduler`: 학습률(learning rate)을 동적으로 변경합니다.\n",
    "* `tf.keras.callbacks.EarlyStopping`: 검증 성능이 향상되지 않으면 훈련을 중지합니다.\n",
    "* `tf.keras.callbacks.TensorBoard`: [텐서보드](https://tensorflow.org/tensorboard)를 사용하여 모델을 모니터링합니다.\n",
    "\n",
    "`tf.keras.callbacks.Callback`을 사용하려면 모델의 `fit` 메서드에 전달합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "rdYwzSYV0E2v"
   },
   "outputs": [],
   "source": [
    "callbacks = [\n",
    "  # `val_loss`가 2번의 에포크에 걸쳐 향상되지 않으면 훈련을 멈춥니다.\n",
    "  tf.keras.callbacks.EarlyStopping(patience=2, monitor='val_loss'),\n",
    "  # `./logs` 디렉토리에 텐서보드 로그를 기록니다.\n",
    "  tf.keras.callbacks.TensorBoard(log_dir='./logs')\n",
    "]\n",
    "model.fit(data, labels, batch_size=32, epochs=5, callbacks=callbacks,\n",
    "          validation_data=(val_data, val_labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 저장과 복원"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "qnl7K-aI0E2z"
   },
   "source": [
    "### 가중치\n",
    "\n",
    "`tf.keras.Model.save_weights`를 사용하여 모델의 가중치를 저장하고 복원합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "uQIANjB94fLB"
   },
   "outputs": [],
   "source": [
    "model = tf.keras.Sequential([\n",
    "layers.Dense(64, activation='relu', input_shape=(32,)),\n",
    "layers.Dense(10, activation='softmax')])\n",
    "\n",
    "model.compile(optimizer=tf.keras.optimizers.Adam(0.001),\n",
    "              loss='categorical_crossentropy',\n",
    "              metrics=['accuracy'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "4eoHJ-ny0E21"
   },
   "outputs": [],
   "source": [
    "# 가중치를 텐서플로의 체크포인트 파일로 저장합니다.\n",
    "model.save_weights('./weights/my_model')\n",
    "\n",
    "# 모델의 상태를 복원합니다.\n",
    "# 모델의 구조가 동일해야 합니다.\n",
    "model.load_weights('./weights/my_model')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "u25Id3xe0E25"
   },
   "source": [
    "기본적으로 모델의 가중치는 [텐서플로 체크포인트](../checkpoints.md) 파일 포맷으로 저장됩니다. 케라스의 HDF5 포맷으로 가중치를 저장할 수도 있습니다(다양한 백엔드를 지원하는 케라스 구현에서는 HDF5가 기본 설정입니다):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "JSAYoFEd0E26"
   },
   "outputs": [],
   "source": [
    "# 가중치를 HDF5 파일로 저장합니다.\n",
    "model.save_weights('my_model.h5', save_format='h5')\n",
    "\n",
    "# 모델의 상태를 복원합니다.\n",
    "model.load_weights('my_model.h5')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "mje_yKL10E29"
   },
   "source": [
    "### 설정\n",
    "\n",
    "모델 설정을 저장하면 가중치는 제외하고 모델의 구조를 직렬화합니다. 원본 모델을 정의한 코드가 없어도 저장된 설정을 사용하여 동일한 구조를 만들고 초기화할 수 있습니다. 케라스는 JSON과 YAML 직렬화 포맷을 지원합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "EbET0oJTzGkq"
   },
   "outputs": [],
   "source": [
    "# 모델을 JSON 포맷으로 직렬화합니다.\n",
    "json_string = model.to_json()\n",
    "json_string"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "pX_badhH3yWV"
   },
   "outputs": [],
   "source": [
    "import json\n",
    "import pprint\n",
    "pprint.pprint(json.loads(json_string))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Q7CIa05r4yTb"
   },
   "source": [
    "JSON 파일로부터 (완전히 새로 초기화된) 모델을 만듭니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "J9UFv9k00E2_"
   },
   "outputs": [],
   "source": [
    "fresh_model = tf.keras.models.model_from_json(json_string)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "t5NHtICh4uHK"
   },
   "source": [
    "YAML 포맷으로 직렬화하려면 *텐서플로를 임포트하기 전에* `pyyaml`을 설치해야 합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "aj24KB3Z36S4"
   },
   "outputs": [],
   "source": [
    "yaml_string = model.to_yaml()\n",
    "print(yaml_string)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "O53Kerfl43v7"
   },
   "source": [
    "YAML 파일로부터 모델을 다시 만듭니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "77yRuwg03_MG"
   },
   "outputs": [],
   "source": [
    "fresh_model = tf.keras.models.model_from_yaml(yaml_string)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "xPvOSSzM0E3B"
   },
   "source": [
    "주의: Model 클래스를 상속하여 만든 모델은 `call` 메서드의 본문에 파이썬 코드로 구조가 정의되어 있기 때문에 직렬화되지 않습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "iu8qMwld4-71"
   },
   "source": [
    "### 전체 모델\n",
    "\n",
    "가중치와 모델 설정, 심지어 옵티마이저 설정까지 포함된 전체 모델을 파일에 저장할 수 있습니다. 모델의 중간 상태를 저장하고 나중에 원본 코드가 없어도 정확히 동일한 상태에서 훈련을 재개할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "45oNY34Z0E3C"
   },
   "outputs": [],
   "source": [
    "# 간단한 모델을 만듭니다.\n",
    "model = tf.keras.Sequential([\n",
    "  layers.Dense(10, activation='softmax', input_shape=(32,)),\n",
    "  layers.Dense(10, activation='softmax')\n",
    "])\n",
    "model.compile(optimizer='rmsprop',\n",
    "              loss='categorical_crossentropy',\n",
    "              metrics=['accuracy'])\n",
    "model.fit(data, labels, batch_size=32, epochs=5)\n",
    "\n",
    "\n",
    "# 전체 모델을 HDF5 파일로 저장합니다.\n",
    "model.save('my_model.h5')\n",
    "\n",
    "# 가중치와 옵티마이저를 포함하여 정확히 같은 모델을 다시 만듭니다.\n",
    "model = tf.keras.models.load_model('my_model.h5')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "케라스 모델의 저장과 직렬화에 대한 더 자세한 내용은 [모델 저장과 직렬화 가이드](./save_and_serialize.ipynb)를 참고하세요."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "PMOWhDOB0E3E"
   },
   "source": [
    "## 즉시 실행\n",
    "\n",
    "[즉시 실행](./eager.md)은 연산을 즉각 평가하는 명령형 프로그래밍(imperative programming) 환경입니다. 케라스에서는 즉시 실행이 필수가 아니지만 `tf.keras`는 이를 지원합니다. 이 기능은 프로그램을 검사하고 디버깅하는데 유용합니다.\n",
    "\n",
    "모든 `tf.keras` 모델링 API는 즉시 실행과 호환됩니다. `Sequential`이나 함수형 API와 사용할 수 있지만 즉시 실행은 특히 *모델 상속*과 *맞춤형 층*을 만들 때 장점이 나타납니다. 이런 API는 (기존의 층을 조합하여 모델을 만드는 대신) 직접 정방향 패스의 코드를 작성하기 때문입니다.\n",
    "\n",
    "[즉시 실행 가이드](./eager.ipynb#build_a_model)에서 맞춤형 훈련 반복과 `tf.GradientTape`를 케라스 모델에 같이 사용하는 예를 참고하세요. 또한 간단하지만 완전한 예제를 [여기](https://github.com/tensorflow/docs/blob/master/site/en/tutorials/quickstart/advanced.ipynb)에서 볼 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 분산 처리"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "2wG3NVco5B5V"
   },
   "source": [
    "### 다중 GPU\n",
    "\n",
    "`tf.keras` 모델은 `tf.distribute.Strategy`를 사용하여 다중 GPU에서 실행할 수 있습니다. 이 API는 기존 코드를 거의 수정하지 않고 다중 GPU에서 훈련을 분산시킬 수 있습니다.\n",
    "\n",
    "현재는 `tf.distribute.MirroredStrategy`가 유일하게 지원되는 분산 전략입니다. `MirroredStrategy`는 한 대의 장치에서 계산 결과를 모두 수집하는 방식인 그래프 내 복제(in-graph replication)를 수행합니다. `distribute.Strategy`를 사용하려면 `Strategy`의 `.scope()` 안에 옵티마이저 객체 생성, 모델 구성, 컴파일 단계를 포함시킨 다음 모델을 훈련합니다.\n",
    "\n",
    "다음 코드는 한 대의 컴퓨터에서 다중 GPU를 사용해 `tf.keras.Model`을 분산 처리하는 예입니다.\n",
    "\n",
    "먼저, `MirroredStrategy`의 `scope()` 안에서 모델을 정의합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "sbaRr7g-0E3I"
   },
   "outputs": [],
   "source": [
    "strategy = tf.distribute.MirroredStrategy()\n",
    "\n",
    "with strategy.scope():\n",
    "  model = tf.keras.Sequential()\n",
    "  model.add(layers.Dense(16, activation='relu', input_shape=(10,)))\n",
    "  model.add(layers.Dense(1, activation='sigmoid'))\n",
    "\n",
    "  optimizer = tf.keras.optimizers.SGD(0.2)\n",
    "\n",
    "  model.compile(loss='binary_crossentropy', optimizer=optimizer)\n",
    "\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "j2hHoMYOgLV-"
   },
   "source": [
    "그다음, 보통 때와 같은 데이터로 모델을 훈련합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "aTuk02nmbvXe"
   },
   "outputs": [],
   "source": [
    "x = np.random.random((1024, 10))\n",
    "y = np.random.randint(2, size=(1024, 1))\n",
    "x = tf.cast(x, tf.float32)\n",
    "dataset = tf.data.Dataset.from_tensor_slices((x, y))\n",
    "dataset = dataset.shuffle(buffer_size=1024).batch(32)\n",
    "\n",
    "model.fit(dataset, epochs=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "FuV_qqysWcDA"
   },
   "source": [
    "더 자세한 정보는 [텐서플로의 분산 훈련 가이드](../distributed_training.ipynb)를 참고하세요."
   ]
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "keras.ipynb",
   "private_outputs": true,
   "provenance": [],
   "toc_visible": true,
   "version": "0.3.2"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
